package com.bac.rentmap.ad.flat.image;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.google.appengine.api.images.*;
import org.springframework.beans.factory.annotation.Required;

import com.bac.rentmap.ad.flat.FlatAd;
import com.bac.rentmap.ad.flat.crawler.FlatCrawledAd;
import com.bac.rentmap.ad.flat.jdo.JdoHelper;
import com.bac.rentmap.jdo.JdoSupport;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.images.Composite.Anchor;

public class JdoFlatImageService extends JdoSupport implements FlatImageService {
    private static final Logger log = Logger
            .getLogger(JdoFlatImageService.class.getName());
    private static final int THUMBNAIL_SIZE = 140;

    private ImagesService imagesService;
    private BlobstoreService blobstoreService;
    private JdoHelper jdoHelper;
    private Image copyright;

    @Override
    public FlatImage upload(FlatImageType imageType, String sessionId,
            BlobKey imageBlob) throws UnsupportedImageTypeException {

        PersistenceManager persistenceManager = getPersistenceManager();
        try {
            Query query = buildQuery(persistenceManager);

            FlatImage flatImage = (FlatImage) query.execute(imageType,
                    sessionId);
            Image resizedImage;
            Image thumbnail;
            try {
                resizedImage = addCopyright(createImageFromBlob(imageBlob));
                thumbnail = createThumbnailFromBlob(createImageFromBlob(imageBlob));

            } catch (Exception e) {
                throw new UnsupportedImageTypeException(flatImage, e);
            }
            if (flatImage == null) {
                log.fine("Creating new FlatImage");
                flatImage = new FlatImage(imageType, sessionId, new Blob(
                        resizedImage.getImageData()), new Blob(
                        thumbnail.getImageData()));
                flatImage.setPublishingDate(new Date());
                persistenceManager.makePersistent(flatImage);
            } else {
                log.fine("Updating existing FlatImage");
                flatImage.setImage(new Blob(resizedImage.getImageData()));
                flatImage.setThumbnail(new Blob(thumbnail.getImageData()));
            }
            return flatImage;
        } finally {
            persistenceManager.close();
            blobstoreService.delete(imageBlob);
        }
    }

    @Override
    public void delete(FlatImageType imageType, String sessionId) {
        PersistenceManager persistenceManager = getPersistenceManager();
        try {
            delete(imageType, sessionId, persistenceManager);
        } finally {
            persistenceManager.close();
        }
    }

    @Override
    public void delete(FlatImageType imageType, String sessionId, PersistenceManager persistenceManager) {
        log.fine("Deleting FlatImage with type " + imageType + ", sessionId: "
                + sessionId);
        Query query = buildQuery(persistenceManager);

        long count = query.deletePersistentAll(imageType, sessionId);
        if (count == 0) {
            log.warning("No flat images deleted");
        }
    }
    
    private Image addCopyright(Image image) throws IOException{
        return image;
//        if(copyright == null){
//            InputStream inputStream =
//                    this.getClass().getResourceAsStream("copyright.jpg");
//            byte[] bytes = inputStreamToBytes(inputStream);
//            copyright = ImagesServiceFactory
//                    .makeImage(bytes);
//        }
//        
//        Composite imageComposite = ImagesServiceFactory.makeComposite(
//                image, 0, 0, 1.0f, Anchor.BOTTOM_RIGHT);
//        
//        Composite copyrightComposite = ImagesServiceFactory.makeComposite(
//                copyright, 0, 0, 0.5f, Anchor.BOTTOM_RIGHT);
//        
//        List<Composite> list = new ArrayList<Composite>();
//        list.add(imageComposite);
//        list.add(copyrightComposite);
//
//        Image composite = imagesService.composite(list, image.getWidth(), image.getHeight(),
//                             0, ImagesService.OutputEncoding.JPEG);
//
//        return composite;
    }
    

    private byte[] inputStreamToBytes(InputStream inputStream)
            throws IOException {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(
                1024);
        byte[] buffer = new byte[1024];
        int length;

        while ((length = inputStream.read(buffer)) >= 0) {
            byteArrayOutputStream.write(buffer, 0, length);
        }

        inputStream.close();
        return byteArrayOutputStream.toByteArray();
    }
    private Image createImageFromBlob(BlobKey imageBlob) {
        return transformImage(imageBlob, 800, 600, 100);
    }

    private Image createThumbnailFromBlob(Image image) {
        image = makeSquare(image);
        return transformImage(image, THUMBNAIL_SIZE, THUMBNAIL_SIZE, 70);
    }

    private Image makeSquare(Image image){
        if(image.getHeight() == image.getWidth())
            return image;
        Transform transform = null;
        if(image.getWidth() > image.getHeight()){
            double leftX = (image.getWidth() - image.getHeight()) / 2.0 / image.getWidth();
            double rightX = leftX + ((double)image.getHeight()) / image.getWidth();
            log.info("1: " + leftX + ", " + rightX);
            transform = ImagesServiceFactory.makeCrop(leftX, 0, rightX, 1);
        }
        else{
            double topY = (image.getHeight() - image.getWidth()) / 2.0 / image.getHeight();
            double bottomY = topY + ((double)image.getWidth()) / image.getHeight();
            log.info("2: " + topY + ", " + bottomY);
            transform = ImagesServiceFactory.makeCrop(0, topY, 1, bottomY);
        }
        Image res = imagesService.applyTransform(transform, image);
        log.info("Making square: " + image.getWidth() + " -> " + res.getWidth() + ", " + image.getHeight() + " -> " + res.getHeight());
        return res;
    }

    private Image transformImage(BlobKey imageBlob, int smallerSize,
            int biggerSize, int quality) {
        Image image = ImagesServiceFactory.makeImageFromBlob(imageBlob);
        return transformImage(image, smallerSize, biggerSize, quality);
    }
    private Image transformImage(Image image, int smallerSize, int biggerSize, int quality){
        // Transform resize;
        // if (image.getWidth() > image.getHeight()) {
        // int height = image.getHeight() * biggerSize / image.getWidth();
        // resize = ImagesServiceFactory.makeResize(biggerSize, height);
        // } else {
        // int width = image.getWidth() * biggerSize / image.getHeight();
        // resize = ImagesServiceFactory.makeResize(width, biggerSize);
        // }
        Transform resize = ImagesServiceFactory.makeResize(smallerSize,
                biggerSize);
//        OutputSettings settings = new OutputSettings(ImagesService.OutputEncoding.JPEG);
//        settings.setQuality(quality);
        return imagesService.applyTransform(resize, image, ImagesService.OutputEncoding.JPEG);
    }

    private Query buildQuery(PersistenceManager persistenceManager) {
        Query query = persistenceManager.newQuery(FlatImage.class);
        query.setFilter("type == typeParam && sessionId == sessionIdParam");
        query.declareParameters("com.bac.rentmap.ad.flat.image.FlatImageType typeParam, String sessionIdParam");
        query.setUnique(true);
        return query;
    }

    @Override
    public void attachToFlatCrawledAd(String sessionId,
            FlatCrawledAd flatCrawledAd) {
        log.fine("Detaching from " + sessionId
                + ", attaching to FlatCrawledAd " + flatCrawledAd);
        PersistenceManager persistenceManager = getPersistenceManager();
        try {
            Query query = buildSessionIdUpdateQuery(persistenceManager);

            @SuppressWarnings("unchecked")
            List<FlatImage> flatImages = (List<FlatImage>) query
                    .execute(sessionId);
            log.fine("About to attach " + flatImages.size() + " entities");
            for (FlatImage flatImage : flatImages) {
                flatImage.setFlat(KeyFactory.createKey("FlatCrawledAd",
                        flatCrawledAd.getId()));
                flatImage.setPublishingDate(flatCrawledAd.getPublishingDate());
            }
        } finally {
            persistenceManager.close();
        }
    }

    @Override
    public void updateFlatAdImages(String sessionId,
                                      FlatAd flatAd) {
        log.fine("Detaching from " + sessionId
                + ", updating FlatAd " + flatAd);
        PersistenceManager persistenceManager = getPersistenceManager();
        try {
            Query query = buildSessionIdUpdateQuery(persistenceManager);

            @SuppressWarnings("unchecked")
            List<FlatImage> newFlatImages = (List<FlatImage>) query
                    .execute(sessionId);
            log.fine("About to update " + newFlatImages.size() + " entities");

            Key flatAdKey = KeyFactory.createKey("FlatAd",
                    flatAd.getId());

            List<FlatImage> oldFlatImages = getFlatImages(flatAdKey, persistenceManager);

            mergeOldAndNewImages(flatAd, flatAdKey, oldFlatImages, newFlatImages, persistenceManager);

        } finally {
            persistenceManager.close();
        }
    }

    private void mergeOldAndNewImages(FlatAd flatAd, Key flatAdKey, List<FlatImage> oldFlatImages, List<FlatImage> newFlatImages, PersistenceManager persistenceManager){

        Map<FlatImageType, FlatImage> oldFlatImageMap = new HashMap<FlatImageType, FlatImage>();
        for (FlatImage oldFlatImage : oldFlatImages) {
            oldFlatImageMap.put(oldFlatImage.getType(), oldFlatImage);
        }

        for (FlatImage newFlatImage : newFlatImages) {
            newFlatImage.setFlat(flatAdKey);
            newFlatImage.setPublishingDate(flatAd.getLastPublishingDate());

            FlatImage oldFlatImage = oldFlatImageMap.get(newFlatImage.getType());
            if(oldFlatImage != null) {
                Long oldFlatImageId = oldFlatImage.getId();
                for (ListIterator<Key> iterator = flatAd.getFlatImages().listIterator(); iterator.hasNext(); ) {
                    Key key = iterator.next();
                    if (key.getId() == oldFlatImageId) {
                        iterator.remove();
                    }
                }
                persistenceManager.deletePersistent(oldFlatImage);
            }
            flatAd.getFlatImages().add(jdoHelper.convertToFlatImageKey(
                    newFlatImage.getId()));
        }
    }

    private Query buildSessionIdUpdateQuery(
            PersistenceManager persistenceManager) {
        Query query = persistenceManager.newQuery(FlatImage.class);
        query.setFilter("sessionId == sessionIdParam");
        query.declareParameters("String sessionIdParam");
        return query;
    }

    @Override
    public void attachToFlatAd(long flatCrawledAdId, long flatAdId,
            Date publishingDate) {
        log.fine("Detaching from " + flatCrawledAdId + ", attaching to FlatAd "
                + flatAdId);
        PersistenceManager persistenceManager = getPersistenceManager();
        try {
            Query query = buildFlatUpdateQuery(persistenceManager);

            @SuppressWarnings("unchecked")
            List<FlatImage> flatImages = (List<FlatImage>) query
                    .execute(jdoHelper
                            .convertToFlatCrawledAdKey(flatCrawledAdId));
            log.fine("About to reattach " + flatImages.size() + " entities");
            for (FlatImage flatImage : flatImages) {
                flatImage.setFlat(KeyFactory.createKey("FlatAd", flatAdId));
                flatImage.setPublishingDate(publishingDate);
            }
        } finally {
            persistenceManager.close();
        }
    }

    private Query buildFlatUpdateQuery(PersistenceManager persistenceManager) {
        Query query = persistenceManager.newQuery(FlatImage.class);
        query.setFilter("flat == flatParam");
        query.declareParameters("com.google.appengine.api.datastore.Key flatParam");
        return query;
    }

    private Map<FlatImageType, FlatImage> convert(List<FlatImage> flatImages) {
        Map<FlatImageType, FlatImage> res = new HashMap<FlatImageType, FlatImage>();
        for (FlatImage flatImage : flatImages) {
            res.put(flatImage.getType(), flatImage);
        }
        return res;
    }

    private Query buildUploadedImagesQuery(PersistenceManager persistenceManager) {
        Query query = persistenceManager.newQuery(FlatImage.class);
        query.setFilter("sessionId == sessionIdParam");
        query.declareParameters("String sessionIdParam");
        return query;
    }

    @Override
    public FlatImage getUploadedImage(String sessionId, FlatImageType imageType) {
        PersistenceManager persistenceManager = getPersistenceManager();
        try {
            Query query = buildQuery(persistenceManager);

            FlatImage res = (FlatImage) query.execute(imageType, sessionId);
            return res;
        } finally {
            persistenceManager.close();
        }
    }

    @Override
    public FlatImage getFlatImage(long id) {
        PersistenceManager persistenceManager = getPersistenceManager();
        try {
            return persistenceManager.getObjectById(FlatImage.class, id);
        } finally {
            persistenceManager.close();
        }
    }

    public void deleteAll(long flatAdId, PersistenceManager persistenceManager) {
        Query query = persistenceManager.newQuery(FlatImage.class);
        query.setFilter("flat == flatParam");
        query.declareParameters("com.google.appengine.api.datastore.Key flatParam");
        query.deletePersistentAll(jdoHelper.convertToFlatAdKey(flatAdId));
    }

    public void deleteAll(String sessionId, PersistenceManager persistenceManager) {
        Query query = buildFlatUpdateQuery(persistenceManager);
        long count = query.deletePersistentAll(sessionId);
        log.fine("Deleted " + count + " images by sessionId " + sessionId);
    }

    @Override
    public void updatePublishingDate(FlatAd flatAd,
            PersistenceManager persistenceManager) {
        Query query = persistenceManager.newQuery(FlatImage.class);
        query.setFilter("flat == flatParam");
        query.declareParameters("com.google.appengine.api.datastore.Key flatParam");
        @SuppressWarnings("unchecked")
        List<FlatImage> flatImages = (List<FlatImage>) query.execute(jdoHelper
                .convertToFlatAdKey(flatAd.getId()));
        for (FlatImage flatImage : flatImages) {
            flatImage.setPublishingDate(flatAd.getLastPublishingDate());
        }
    }

    @Override
    public List<Long> getFlatImageIds(Key flatKey,
                                      PersistenceManager persistenceManager) {
        Query query = persistenceManager.newQuery("select id from "
                + FlatImage.class.getName());
        query.setFilter("flat == flatParam");
        query.declareParameters("com.google.appengine.api.datastore.Key flatParam");
        query.setOrdering("orderNo asc");

        @SuppressWarnings("unchecked")
        List<Long> res = (List<Long>) query.execute(flatKey);
        log.fine("Found " + res.size() + " images by key " + flatKey);
        return res;
    }

    @Override
    public List<FlatImage> getFlatImages(Key flatKey,
                                      PersistenceManager persistenceManager) {
        Query query = persistenceManager.newQuery(FlatImage.class);
        query.setFilter("flat == flatParam");
        query.declareParameters("com.google.appengine.api.datastore.Key flatParam");
        query.setOrdering("orderNo asc");

        @SuppressWarnings("unchecked")
        List<FlatImage> res = (List<FlatImage>) query.execute(flatKey);
        log.fine("Found " + res.size() + " images by key " + flatKey);
        return res;
    }

    @Required
    public void setImagesService(ImagesService imagesService) {
        this.imagesService = imagesService;
    }

    @Required
    public void setJdoHelper(JdoHelper jdoHelper) {
        this.jdoHelper = jdoHelper;
    }

    @Required
    public void setBlobstoreService(BlobstoreService blobstoreService) {
        this.blobstoreService = blobstoreService;
    }
}
